#include "view.h"
#include "wlroots-full.hpp"
#include "output_wrapper.h"
#include "workspace.h"
#include "seat_wrapper.h"

#include <algorithm>

View::View(struct wlr_surface* surface, view_type type,
           ViewInterface* view_impl,
           Server *server)
    : m_server(server)
    , m_surface(surface)
    , m_type(type)
    , m_impl(view_impl)
{
    m_workspace = m_server->m_workspace_current;
    m_scene_tree = wlr_scene_tree_create(m_workspace->tree());

    m_map.connect(&m_surface->events.map, [=](void* data) {
        map();
    });

    m_unmap.connect(&m_surface->events.unmap, [=](void* data) {
        unmap(true);
    });

    wlr_scene_node_set_enabled(&m_scene_tree->node, false);
}

void View::set_output(OutputWrapper *output)
{
    // assert(!view->fullscreen);
    if (!output->is_usable()) {
        wlr_log(WLR_ERROR, "invalid output set for view");
        return;
    }
    m_output = output;
}

wlr_surface *View::surface_at(double vx, double vy, double *sx, double *sy)
{
    return m_impl->surface_at(vx, vy, sx, sy);
}

wlr_box View::geometry()
{
    struct wlr_box b = get_surface_geometry();
    /* Add XDG view invisible border if it exists */
    b.width += 2 * b.x;
    b.height += 2 * b.y;
    /* Make co-ordinates relative to screen */
    b.x = m_x;
    b.y = m_y;
    return b;
}

void View::resize(wlr_box geo)
{
    m_impl->resize(geo);
}

void View::focus()
{
    if (!m_surface)
        return;

    auto seat = m_server->m_seat->seat();

    struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface;
    if (prev_surface == m_surface) {
        /* Don't re-focus an already focused surface. */
        return;
    }

    if (m_impl)
        m_impl->focus();

    if (prev_surface && m_impl)
        m_impl->set_activated(prev_surface, false);

    move_to_front();
    if (m_impl)
        m_impl->set_activated(m_surface, true);
    /*
     * Tell the seat to have the keyboard enter this surface. wlroots will
     * keep track of this and automatically send key events to the
     * appropriate clients without additional work on your part.
     */
    // struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat);
    // wlr_seat_keyboard_notify_enter(seat, m_surface, keyboard->keycodes,
    //                                keyboard->num_keycodes,
    //                                &keyboard->modifiers);
}

bool View::is_toplevel()
{
    return m_impl->is_toplevel();
}

void View::impl_map()
{

}

void View::destroy()
{

}

void View::foreign_toplevel_handle_create()
{
    m_toplevel.handle = wlr_foreign_toplevel_handle_v1_create(
        m_server->m_foreign_toplevel_manager);
    if (!m_toplevel.handle) {
        // wlr_log(WLR_ERROR, "cannot create foreign toplevel handle for (%s)",
                // view_get_string_prop(view, "title"));
        return;
    }

#if 0
    toplevel->maximize.notify = handle_request_maximize;
    wl_signal_add(&toplevel->handle->events.request_maximize,
                  &toplevel->maximize);

    toplevel->minimize.notify = handle_request_minimize;
    wl_signal_add(&toplevel->handle->events.request_minimize,
                  &toplevel->minimize);

    toplevel->fullscreen.notify = handle_request_fullscreen;
    wl_signal_add(&toplevel->handle->events.request_fullscreen,
                  &toplevel->fullscreen);

    toplevel->activate.notify = handle_request_activate;
    wl_signal_add(&toplevel->handle->events.request_activate,
                  &toplevel->activate);

    toplevel->close.notify = handle_request_close;
    wl_signal_add(&toplevel->handle->events.request_close,
                  &toplevel->close);

    toplevel->destroy.notify = handle_destroy;
    wl_signal_add(&toplevel->handle->events.destroy, &toplevel->destroy);
#endif
}

void View::desktop_focus(bool raise)
{
#if 1
    /*
     * Guard against views with no mapped surfaces when handling
     * 'request_activate' and 'request_minimize'.
     */
    if (!m_surface) {
        return;
    }

    if (m_minimized) {
        /*
         * Unminimizing will map the view which triggers a call to this
         * function again (with raise=true).
         */
        minimize(false);
        return;
    }

    if (!m_mapped) {
        return;
    }

    /*
     * Switch workspace if necessary to make the view visible
     * (unnecessary for "always on {top,bottom}" views).
     */
    // if (!view_is_always_on_top(view) && !view_is_always_on_bottom(view)) {
    //     workspaces_switch_to(view->workspace, /*update_focus*/ false);
    // }

    /*
     * Give input focus, even if the view claims not to want it (see
     * view->impl->wants_focus). This is a workaround for so-called
     * "globally active" X11 views (MATLAB known to be one such)
     * that expect to be able to control focus themselves, but can't
     * under labwc since it's disallowed at the wlroots level.
     */
#if 1
    if (m_surface != m_server->m_seat->seat()->keyboard_state.focused_surface) {
        m_server->m_seat->focus_surface(m_surface);
    }
#endif

    if (raise) {
        move_to_front();
    }
#endif
}

wlr_box View::get_surface_geometry()
{
    return m_impl->get_surface_geometry();
}

void View::configure(wlr_box geo)
{

}

void View::close()
{

}

const char *View::get_string_prop(const char *prop)
{

    return "";
}

void View::map()
{
    if (m_mapped) {
        return;
    }
    m_mapped = true;
    m_impl->map();

    void *p  = m_surface;

    desktop_focus(/*raise*/ true);
#if 0
    view_update_title(view);
    view_update_app_id(view);
    if (!view->been_mapped) {
        window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP);
    }

    /*
     * It's tempting to just never create the foreign-toplevel handle in the
     * map handlers, but the app_id/title might not have been set at that
     * point, so it's safer to process the property here
     */
    enum property ret = window_rules_get_property(view, "skipTaskbar");
    if (ret == LAB_PROP_TRUE) {
        if (view->toplevel.handle) {
            wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle);
        }
    }

    wlr_log(WLR_DEBUG, "[map] identifier=%s, title=%s\n",
            view_get_string_prop(view, "app_id"),
            view_get_string_prop(view, "title"));
#endif

    m_been_mapped = true;
}

void View::set_fullscreen(bool fullscreen)
{

}

void View::notify_tiled()
{

}

void View::unmap(bool client_request)
{
    m_mapped = false;
    auto view = next_toplevel();
    view->focus();
}

void View::maximize(bool maximize)
{

}

void View::minimize(bool minimized)
{
    /*
     * Minimize the root window first because some xwayland clients send a
     * request-unmap to sub-windows at this point (for example gimp and its
     * 'open file' dialog), so it saves trying to unmap them twice
     */
    View *root = get_root();
    root->_minimize(minimized);
    // minimize_sub_views(root, minimized);
}

void View::move_to_front()
{

}

void View::move_to_back()
{

}

View *View::get_root()
{
    return m_impl->get_root();
}

void View::append_children(wl_array *children)
{

}

bool View::is_related(wlr_surface *surface)
{

    return false;
}

view_size_hints View::get_size_hints()
{

    return {};
}

view_wants_focus View::wants_focus()
{
    return VIEW_WANTS_FOCUS_NEVER;
}

bool View::has_strut_partial()
{
    return false;
}

void View::_minimize(bool minimized)
{
    if (m_minimized == minimized) {
        return;
    }
    if (m_toplevel.handle) {
        wlr_foreign_toplevel_handle_v1_set_minimized(m_toplevel.handle, minimized);
    }
    if (m_impl) {
        m_impl->minimize(minimized);
    }
    m_minimized = minimized;
    if (m_minimized) {
        m_impl->unmap(/* client_request */ false);
    } else {
        map();
    }
}

View *View::next_toplevel()
{
    // m_server->m_views;
    // m_server->m_views

    //TODO: to use boost::intrusive::list

    auto it = std::find(m_server->m_views.begin(), m_server->m_views.end(), this);
    assert(it != m_server->m_views.end());

    View* view;
    do {
        view = *it;
        if (view->m_been_mapped)
            continue;
        if (view->is_toplevel())
            continue;
        break;
    } while (it != m_server->m_views.end());

    return view;
}
